package controllers

import (
	"cvevulner/common"
	"cvevulner/models"
	"cvevulner/taskhandler"
	"cvevulner/util"
	"fmt"
	"github.com/astaxie/beego"
	"github.com/astaxie/beego/logs"
	"math/rand"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"
)

//FileController file operation routing processing
type FileController struct {
	beego.Controller
}

//DownloadLastExcel Download the latest excel file
// @router /lastExcel [get]
func (f *FileController) DownloadLastExcel() {
	fd := beego.AppConfig.DefaultString("fileDir", "download")
	err := common.CreateDir(fd)
	er := models.ExportRecord{}
	err = er.QueryLast()
	if err != nil {
		_ = f.Ctx.Output.Body([]byte("no file "))
	}
	fp := filepath.Join(fd, er.FileName)
	if ex, _ := util.IsExistPath(fp); !ex {
		_ = f.Ctx.Output.Body([]byte("no file "))
	}
	f.Ctx.Output.Download(fp, er.FileName)
}

//DownLoadExcelByFileCode download excel file by code
//@router /downloadExcel
func (f *FileController) DownLoadExcelByFileCode() {
	fd := beego.AppConfig.DefaultString("fileDir", "download")
	common.CreateDir(fd)
	fc := f.GetString("fileCode")
	if fc == "" {
		_ = f.Ctx.Output.Body([]byte("err: fileCode is a required parameter "))
		return
	}
	er := models.ExportRecord{FileCode: fc}
	err := er.Read("file_code")
	if err != nil {
		_ = f.Ctx.Output.Body([]byte(fmt.Sprintf("err: %v", err)))
		return
	}
	if er.FileName == "" {
		_ = f.Ctx.Output.Body([]byte("err: Can not find excel file by fileCode! "))
		return
	}
	if er.State == 0 {
		_ = f.Ctx.Output.Body([]byte("The file is being generated, please try again later!"))
		return
	}
	if er.State == 2 {
		_ = f.Ctx.Output.Body([]byte("File generation failed, please contact the administrator or regenerate!!"))
		return
	}
	fp := filepath.Join(fd, er.FileName)
	if ex, _ := util.IsExistPath(fp); !ex {
		_ = f.Ctx.Output.Body([]byte("error:file does not exist"))
	}
	f.Ctx.Output.Download(fp, er.FileName)
}

//TriggerCveData touch off generate cve data excel and get cve package
//@router /triggerCveData [get]
func (f *FileController) TriggerCveData() {
	// Limit on the number of triggers
	nameStr, limitCount := LimitTriggerSa()
	if limitCount != 0 {
		cha := fmt.Sprintf("The number of requests is too frequent, please try again later, " +
			"there is currently a task being processed")
		f.Ctx.WriteString(cha)
		UpdateLimitTriggerSa(nameStr, 2)
		return
	}
	startTime := f.GetString("startTime")
	if startTime == "" {
		f.Ctx.WriteString("Error: startTime cannot be empty")
		UpdateLimitTriggerSa(nameStr, 2)
		return
	}
	typeName := f.GetString("typeName")
	if len(typeName) < 2 {
		f.Ctx.WriteString("Error: typeName cannot be empty")
		UpdateLimitTriggerSa(nameStr, 2)
		return
	}
	legalBool := false
	elt := models.EmailList{EmailType: 1}
	el, eltErr := elt.Read("EmailType")
	if eltErr != nil {
		logs.Error("Failed to get mailing list, err: ", eltErr)
		legalBool = true
	}
	for _, em := range el {
		if strings.Contains(em.EmailName, "@") {
			if strings.Split(em.EmailName, "@")[0] == typeName {
				legalBool = true
			}
		} else {
			legalBool = true
		}
	}
	if !legalBool {
		f.Ctx.WriteString("Error: The typeName parameter value is wrong")
		UpdateLimitTriggerSa(nameStr, 2)
		return
	}
	accessToken := os.Getenv("GITEE_TOKEN")
	if accessToken == "" || len(accessToken) < 1 {
		logs.Error("TriggerCveData, Issue token acquisition failed, "+
			"current time: ", common.GetCurTime())
		f.Ctx.WriteString("Error: Service internal error, try again later")
		UpdateLimitTriggerSa(nameStr, 2)
		return
	}
	owner := beego.AppConfig.String("gitee::owner")
	// proc OpenEulerSaNum
	taskhandler.UpdateUseOpenEulerSaNum()
	rt := regexp.MustCompile(`^(\d{4})-\d{2}-(\d{2})$`)
	find := rt.Match([]byte(startTime))
	if !find {
		f.Ctx.WriteString(`Error: please enter the correct start time in a format like this "yyyy-MM-dd".`)
		UpdateLimitTriggerSa(nameStr, 2)
		return
	}
	unaffectYear, yerr := beego.AppConfig.Int("excel::unaffect_year")
	if yerr != nil {
		unaffectYear = 2018
	}
	dir := beego.AppConfig.DefaultString("fileDir", "download")
	common.CreateDir(dir)
	var wgCrvf sync.WaitGroup
	componentMap := make(map[string]taskhandler.ComponentInfo)
	cvrfFileList := make(map[string][]string)
	cvrfFileMap := make(map[string]taskhandler.CvrfSa)
	affectBranchSlice := make([]string, 0)
	var unaffectcvrf taskhandler.UnaffectCvrfSa
	cvrffileName := filepath.Join(dir, "cvrf-unaffected-cve-"+common.GetCurDate()+".xml")
	cvrfFileSlice, cvrfOk := cvrfFileList[taskhandler.UNAFFECTCVRFKEY]
	if cvrfOk && len(cvrfFileSlice) > 0 {
		cvrffileName = cvrfFileSlice[0]
	} else {
		cvrfNameSlice := make([]string, 0)
		cvrfNameSlice = append(cvrfNameSlice, cvrffileName)
		cvrfFileList[taskhandler.UNAFFECTCVRFKEY] = cvrfNameSlice
	}
	updateInfoSlice := make([]taskhandler.UpdateInfoXml, 0)
	go f.Ctx.WriteString("Processing: Data is being prepared, please wait patiently, Please check your mail later!\n")
	unaffectcvrf.Xmlns = "http://www.icasi.org/CVRF/schema/cvrf/1.1"
	unaffectcvrf.XmlnsCvrf = "http://www.icasi.org/CVRF/schema/cvrf/1.1"
	du := "openEuler-20.03-LTS@https://gitee.com/unsunghero/obs_pkg_rpms/raw/master/latest_rpm/openEuler-20.03-LTS.csv"
	//du := beego.AppConfig.String("excel::v_pack_20_03_url")
	du = beego.AppConfig.DefaultString("excel::v_pack_20_03_url", du)
	csvPathList := strings.Split(du, ";")
	if len(csvPathList) > 0 {
		for _, csvP := range csvPathList {
			openBranchx := strings.Split(csvP, "@")
			if len(openBranchx) == 2 {
				affectBranch := openBranchx[0]
				taskhandler.UnaffectIssueProc(affectBranch, cvrfFileList,
					componentMap, startTime, accessToken, owner, &unaffectcvrf, unaffectYear)
				affectBranchSlice = append(affectBranchSlice, affectBranch)
				csvDownPath := openBranchx[1]
				now := time.Now().Unix()
				en := fmt.Sprintf("cve与安全公告%v_%v.xlsx", openBranchx[0], now)
				fileCode := common.EncryptMd5(en)
				wgCrvf.Add(1)
				updateInfoxml := taskhandler.GenerateExcelTrigger(&wgCrvf, en, startTime, fileCode, affectBranch,
					csvDownPath, dir, cvrfFileList, componentMap, cvrfFileMap)
				updateInfoSlice = append(updateInfoSlice, updateInfoxml)
			}
		}
	}
	wgCrvf.Wait()
	taskhandler.WriteUnaffectCvrfXml(cvrffileName, &unaffectcvrf)
	taskhandler.SaveCvrfName(cvrfFileList, cvrffileName, taskhandler.UNAFFECTCVRFKEY)
	// Generate cvrf file
	GenAndUploadCvrf(cvrfFileMap, cvrfFileList, componentMap, affectBranchSlice, dir)
	// Return the result first, continue processing the data
	UpdateLimitTriggerSa(nameStr, 1)
	GenUpdateInfoXmlFile(updateInfoSlice, dir)
}

// Generate updateinfo xml file
func GenUpdateInfoXmlFile(updateInfoSlice []taskhandler.UpdateInfoXml, dir string) {
	if len(updateInfoSlice) > 0 {
		updateinfoDir := beego.AppConfig.String("obs::upload_updateinfo_dir")
		obsDir := updateinfoDir + common.GetCurDate() + "/"
		dirErr, objectDir := taskhandler.ObsCreateDir(obsDir)
		for _, upInfo := range updateInfoSlice {
			// write excel
			if len(upInfo.XmfilePath) > 1 {
				fileNameList := make([]string, 0)
				fileExt := filepath.Ext(upInfo.XmfilePath)
				excelName := upInfo.XmfilePath[:(len(upInfo.XmfilePath)-len(fileExt))] + ".xlsx"
				taskhandler.CreateUpdateExcel(excelName)
				zipFileList := []string{upInfo.FileName, upInfo.XmfilePath}
				fileNameList = append(fileNameList, excelName)
				zipFileList = append(zipFileList, excelName)
				// write xml
				taskhandler.WriteXml(upInfo.XmfilePath, excelName, upInfo.AffectBranch, upInfo.Cvexml,
					upInfo.Dpdates, upInfo.SecurityNotice, upInfo.PackRpmx)
				// zip file
				if isExist := taskhandler.FileExist(zipFileList); isExist {
					zipFileName := upInfo.AffectBranch + "_" + common.GetCurDate() + "_" + strconv.Itoa(time.Now().Nanosecond()) + ".zip"
					zipFileName = filepath.Join(dir, zipFileName)
					zipErr := taskhandler.ZipFiles(zipFileName, zipFileList, dir, dir)
					if zipErr != nil {
						logs.Error("File compression failed: err: ", zipErr)
					}
					// send email
					sendError := taskhandler.SendEmail(zipFileName, 0, "", "")
					if sendError != nil {
						logs.Error("SendEmail, sendErr: ", sendError)
						return
					}
					zipFileList = append(zipFileList, zipFileName)
				}
				fileNameList = append(fileNameList, upInfo.XmfilePath)
				// Upload to obs
				if dirErr == nil && len(fileNameList) > 0 {
					for _, fl := range fileNameList {
						_, xmlFileName := filepath.Split(fl)
						objectName := objectDir + xmlFileName
						upErr := taskhandler.ObsUploadFile(objectName, fl)
						if upErr != nil {
							logs.Error("upErr: ", upErr)
						}
					}
				}
				// Clear file
				taskhandler.DelFile(zipFileList)
			}
		}
	}
}

// Limit on the number of triggers
func LimitTriggerSa() (string, int) {
	var captcha string
	for i := 0; i < 6; i++ {
		//产生0到9的整数
		num := rand.Intn(10)
		//将整数转为字符串
		captcha += strconv.Itoa(num)
	}
	now := time.Now().UnixNano()
	en := fmt.Sprintf("cve与安全公告%v_%v.xlsx", captcha, now)
	timeUnix := time.Now().Unix()
	//It is time-consuming to generate excel, here is the current limit processing
	er := models.ExportRecord{}
	err := er.QueryLast()
	if err == nil && (timeUnix-er.CreateTime < 300) {
		logs.Error("The number of requests is too frequent, please try again later, " +
			"there is currently a task being processed")
		return "", 1
	} else {
		rand.Seed(now)
		fileCode := common.EncryptMd5(en)
		er = models.ExportRecord{FileName: en, FileCode: fileCode, State: 0, CreateTime: timeUnix}
		err = er.Insert()
		logs.Error("err: ", err)
	}
	return en, 0
}

func UpdateLimitTriggerSa(fileName string, state int8) {
	fr := models.ExportRecord{FileName: fileName}
	err := fr.Read("file_name")
	if err != nil {
		logs.Error("fr.Read, err: ", err)
		return
	}
	fr.State = state
	_ = fr.Update("state")
}

// generate and upload cvrf.xml
func GenAndUploadCvrf(cvrfFileMap map[string]taskhandler.CvrfSa,
	cvrfFileList map[string][]string, componentMap map[string]taskhandler.ComponentInfo,
	affectBranchSlice []string, dir string) {
	common.CreateDir(dir)
	writeCvrfSlice := make([]string, 0)
	totalFileSlice := make([]string, 0)
	if len(componentMap) > 0 {
		for comKey, comValue := range componentMap {
			if len(affectBranchSlice) > 1 {
				isEque := true
				branchInfo1 := comValue.CveNumMap[affectBranchSlice[0]].CveNumSlice
				for _, branchInfo := range affectBranchSlice[1:] {
					if !common.CompareSlice(branchInfo1, comValue.CveNumMap[branchInfo].CveNumSlice) {
						isEque = false
						break
					}
				}
				if isEque {
					cvrfFileName := componentMap[comKey].CvrfFileName
					cvrfVule := cvrfFileMap[cvrfFileName]
					md5Ok, tmpOpenEulerSANum := taskhandler.QueryCveMd5(componentMap[comKey].CveNum, componentMap[comKey].OwnedComponent,
						componentMap[comKey].OpenEulerSANum, taskhandler.FIXEDFLAGE)
					taskhandler.WriteCvrfXml(cvrfFileName, &cvrfVule)
					writeCvrfSlice = append(writeCvrfSlice, cvrfFileName)
					if md5Ok {
						UpdateOpenEulerSaNumStatus(tmpOpenEulerSANum)
						var saf models.SaFileList
						saf.FileName = "cvrf-" + tmpOpenEulerSANum + ".xml"
						models.DeleteCvrfFileName(&saf, "FileName")
					}
					for _, br := range comValue.CveNumMap {
						UpdateOpenEulerSaNumStatus(br.OpenEulerSANum)
					}
				} else {
					for _, br := range comValue.CveNumMap {
						cvrfFileName := br.CvrfFileName
						cvrfVule := cvrfFileMap[br.CvrfFileName]
						md5Ok, tmpOpenEulerSANum := taskhandler.QueryCveMd5(br.CveNumSlice, componentMap[comKey].OwnedComponent,
							br.OpenEulerSANum, taskhandler.FIXEDFLAGE)
						taskhandler.WriteCvrfXml(cvrfFileName, &cvrfVule)
						writeCvrfSlice = append(writeCvrfSlice, cvrfFileName)
						if md5Ok {
							UpdateOpenEulerSaNumStatus(tmpOpenEulerSANum)
							var saf models.SaFileList
							saf.FileName = "cvrf-" + tmpOpenEulerSANum + ".xml"
							models.DeleteCvrfFileName(&saf, "FileName")
						}
					}
					UpdateOpenEulerSaNumStatus(comValue.OpenEulerSANum)
				}
			} else {
				cvrfFileName := componentMap[comKey].CvrfFileName
				cvrfVule := cvrfFileMap[cvrfFileName]
				md5Ok, tmpOpenEulerSANum := taskhandler.QueryCveMd5(componentMap[comKey].CveNum, componentMap[comKey].OwnedComponent,
					componentMap[comKey].OpenEulerSANum, taskhandler.FIXEDFLAGE)
				taskhandler.WriteCvrfXml(cvrfFileName, &cvrfVule)
				writeCvrfSlice = append(writeCvrfSlice, cvrfFileName)
				if md5Ok {
					UpdateOpenEulerSaNumStatus(tmpOpenEulerSANum)
					var saf models.SaFileList
					saf.FileName = "cvrf-" + tmpOpenEulerSANum + ".xml"
					models.DeleteCvrfFileName(&saf, "FileName")
				}
				for _, br := range comValue.CveNumMap {
					UpdateOpenEulerSaNumStatus(br.OpenEulerSANum)
				}
			}
		}
	}
	cvrfFileList[taskhandler.CVRFFKEY] = writeCvrfSlice
	uploadCvrfFile(cvrfFileList, totalFileSlice, dir, componentMap)
}

func uploadCvrfFile(cvrfFileList map[string][]string, totalFileSlice []string, dir string,
	componentMap map[string]taskhandler.ComponentInfo) {
	uploadCvrfDir := beego.AppConfig.String("obs::upload_cvrf_dir")
	downloadCvrfDir := beego.AppConfig.String("obs::download_cvrf_dir")
	obsDir := uploadCvrfDir + common.GetCurDate() + "/"
	dirErr, objectDir := taskhandler.ObsCreateDir(obsDir)
	if dirErr != nil {
		logs.Error("dirErr: ", dirErr)
	}
	indexFilePath := filepath.Join(dir, "index.txt")
	indexObjectName := downloadCvrfDir + "index.txt"
	downObsErr := taskhandler.ObsDownloadFile(indexObjectName, indexFilePath)
	if downObsErr != nil {
		logs.Error("downObsErr: ", downObsErr)
		return
	}
	saFileStr := taskhandler.ReadFileAll(indexFilePath)
	SaveFileToDb(saFileStr)
	totalSlice := make([]string, 0)
	saNumber := int64(1000)
	sfl := models.GetCvrfAllFile()
	curYears := strconv.Itoa(time.Now().Year())
	saDir := beego.AppConfig.DefaultString("saFileDir", "download/sa")
	common.CreateAllDir(saDir)
	if len(sfl) > 0 {
		for _, l := range sfl {
			fSlice := strings.Split(l.FileName, "-")
			dirYears := curYears
			if len(fSlice) > 4 {
				dirYears = fSlice[3]
			}
			totalSlice = append(totalSlice, dirYears+"/"+l.FileName)
			saNumber = l.SaNumber
		}
	}
	// File name and data stored in database
	fileSlice, fOk := cvrfFileList[taskhandler.CVRFFKEY]
	if fOk {
		totalFileSlice = UploadSaFile(fileSlice, totalSlice, totalFileSlice, saNumber,
			curYears, saDir, dir, objectDir)
	}
	unaffFileSlice, unffOk := cvrfFileList[taskhandler.UNAFFECTCVRFKEY]
	if unffOk {
		subFileSlice := make([]string, 0)
		updateFilePath := filepath.Join(dir, "update_unaffect.txt")
		for _, fPath := range unaffFileSlice {
			//taskhandler.WriteUnaffectCvrfXml(fPath, componentMap[fPath].UnaffectFile)
			_, fileName := filepath.Split(fPath)
			// File storage to db
			recordErr := taskhandler.RecordCrvfInfo(fPath, fileName, "", taskhandler.UNAFFECTFLAG)
			logs.Info("recordErr: ", recordErr)
			// Upload file, pending
			subFileSlice = append(subFileSlice, fileName)
			totalFileSlice = append(totalFileSlice, fPath)
			// Upload successfully, modify file status
			if len(fileName) > 5 {
				openEulerSANum := fileName[5 : len(fileName)-4]
				taskhandler.UpdateCvrfRecord(openEulerSANum, 2)
			}
		}
		taskhandler.ReadWriteFile(updateFilePath, subFileSlice)
		totalFileSlice = append(totalFileSlice, updateFilePath)
	}
	if len(totalFileSlice) > 0 {
		for _, localFilePath := range totalFileSlice {
			_, localFileName := filepath.Split(localFilePath)
			obsFilePath := objectDir + localFileName
			obsErr := taskhandler.PostFile(localFilePath, obsFilePath)
			if obsErr != nil {
				logs.Error("obsErr: ", obsErr)
			}
		}
		dir := "download"
		zipFileName := "cvrf-" + common.GetCurDate() + "_" + strconv.Itoa(time.Now().Nanosecond()) + ".zip"
		zipFileName = filepath.Join(dir, zipFileName)
		zipErr := taskhandler.ZipFiles(zipFileName, totalFileSlice, dir, dir)
		if zipErr != nil {
			logs.Error("File compression failed: err: ", zipErr)
		}
		// send email
		sendError := taskhandler.SendEmail(zipFileName, 1, "", "")
		if sendError != nil {
			logs.Error("SendEmail, sendErr: ", sendError)
			return
		}
		totalFileSlice = append(totalFileSlice, zipFileName)
	}
	// Delete local files
	taskhandler.DelFile(totalFileSlice)
	logs.Info(cvrfFileList[taskhandler.BRANCHSKEY], ",End of generating cvrf format file this time")
}

func UploadSaFile(fileSlice, totalSlice, totalFileSlice []string, saNumber int64,
	curYears, saDir, dir, uploadPath string) []string {
	subFileSlice := make([]string, 0)
	oldFileSlice := make([]string, 0)
	for _, fPath := range fileSlice {
		_, fileName := filepath.Split(fPath)
		oldSa := RegSa(fileName)
		saNumber = saNumber + 1
		oldText := fmt.Sprintf("openEuler-SA-%v-%v", curYears, oldSa)
		newText := fmt.Sprintf("openEuler-SA-%v-%v", curYears, saNumber)
		newFile := fmt.Sprintf("cvrf-%v.xml", newText)
		newPath := filepath.Join(saDir, newFile)
		ReplaceFileSa(fPath, newPath, oldText, newText)
		oldFileSlice = append(oldFileSlice, fPath)
		// File storage to db
		recordErr := taskhandler.RecordCrvfInfo(fPath, fileName, newText, taskhandler.FIXEDFLAGE)
		logs.Info("recordErr: ", recordErr)
		// Upload file, pending
		SaveFileRecord(newFile)
		totalSlice = append(totalSlice, curYears+"/"+newFile)
		taskhandler.ProcCvrfFileName(fileName)
		subFileSlice = append(subFileSlice, curYears+"/"+newFile)
		totalFileSlice = append(totalFileSlice, newPath)
		// Upload successfully, modify file status
		if len(newFile) > 5 {
			openEulerSANum := newFile[5 : len(newFile)-4]
			taskhandler.UpdateCvrfRecord(openEulerSANum, 2)
		}
	}
	indexFilePath := filepath.Join(dir, "index.txt")
	updateFilePath := filepath.Join(dir, "update_fixed.txt")
	readErr := taskhandler.ReadWriteFile(indexFilePath, totalSlice)
	if readErr != nil {
		logs.Error(indexFilePath, readErr)
	}
	readErr = taskhandler.ReadWriteFile(updateFilePath, subFileSlice)
	if readErr != nil {
		logs.Error(updateFilePath, readErr)
	}
	totalFileSlice = append(totalFileSlice, indexFilePath)
	totalFileSlice = append(totalFileSlice, updateFilePath)
	taskhandler.DelFile(oldFileSlice)
	return totalFileSlice
}

func UpdateOpenEulerSaNumStatus(openEulerSANum string) {
	var sa models.SaNumber
	curYears := strconv.Itoa(time.Now().Year())
	sa.SaYears = curYears
	sa.OpenEulerSANum = openEulerSANum
	sa.UpdateTime = common.GetCurTime()
	getErr := models.GetSaNumber(&sa, "OpenEulerSANum", "SaYears")
	if getErr == nil {
		sa.Status = 1
		models.UpdateSaNumber(&sa, "status")
	}
}

func RegSa(bufVule string) string {
	subSlice1 := strings.Split(bufVule, ".")
	if len(subSlice1) > 0 {
		subSlice2 := strings.Split(subSlice1[0], "-")
		if len(subSlice2) > 0 {
			result := subSlice2[len(subSlice2)-1]
			return result
		}
	}
	return ""
}

func SaveFileToDb(saFileStr string) {
	if len(saFileStr) > 1 {
		saFileSlice := strings.Split(saFileStr, "\n")
		if len(saFileSlice) > 0 {
			models.DeleteCvrfFileRecord()
			for _, saf := range saFileSlice {
				if len(saf) > 2 {
					singSlice := strings.Split(saf, "/")
					if len(singSlice) > 1 {
						SaveFileRecord(singSlice[1])
					}
				}
			}
		}
	}
}

func SaveFileRecord(fileName string) {
	var af models.SaFileRecord
	result2 := RegSa(fileName)
	saNumber, _ := strconv.ParseInt(result2, 10, 64)
	af.SaNumber = saNumber
	af.Status = 1
	af.FileName = fileName
	af.CreateTime = common.GetCurTime()
	models.InsertCvrfFileRecord(&af)
}

func ReplaceFileSa(oldPath, newPath, oldText, newText string) {
	helper := taskhandler.ReplaceHelper{
		OldPath: oldPath,
		NewPath: newPath,
		OldText: oldText,
		NewText: newText,
	}
	err := helper.DoWrok()
	if err == nil {
		fmt.Println("done!")
	} else {
		fmt.Println("error:", err.Error())
	}
}
